/*
 * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ota_event.h"
#include <iostream>
#include <string>
#include <cstdint>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include "beget_ext.h"
#include "init_utils.h"

#ifdef OTA_ROLLBACK_SUPPORT

#endif

#ifdef OTA_ROLLBACK_SUPPORT
const off_t PARTITION_ACTIVE_SLOT_OFFSET = 1024;
const off_t PARTITION_ACTIVE_SLOT_SIZE = 4;
const off_t PARTITION_CHECKPOINT_OFFSET = 1032;
const off_t PARTITION_CHECKPOINT_SIZE = 4;

static int SetCheckpointToBootctrl(off_t offset, off_t size, int checkpoint)
{
    char bootctrlDev[MAX_BUFFER_LEN] = {0};
    // Get bootctrl device path
    if (GetBlockDevicePath("/bootctrl", bootctrlDev, MAX_BUFFER_LEN) != 0) {
        BEGET_LOGE("Failed to get bootctrl device");
        return -1;
    }
    char *realPath = GetRealPath(bootctrlDev);
    if (realPath == NULL) {
        BEGET_LOGE("Failed to get bootctrl device real path");
        return -1;
    }
    int fd = open(realPath, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    free(realPath);
    if (fd < 0) {
        BEGET_LOGE("Failed to open bootctrl device, errno %d", errno);
        return -1;
    }
    if (lseek(fd, offset, SEEK_SET) < 0) {
        close(fd);
        BEGET_LOGE("Failed to lseek bootctrl device fd, errno %d", errno);
        return -1;
    }
    if (write(fd, &checkpoint, sizeof(checkpoint)) != size) {
        close(fd);
        BEGET_LOGE("Failed to write current checkpoint to bootctrl, errno %d", errno);
        return -1;
    }
    close(fd);
    BEGET_LOGI("set checkpoint: %d", checkpoint);
    return 0;
}

static int SetSlotInfoToBootctrl(off_t offset, off_t size, int slotInfo)
{
    char bootctrlDev[MAX_BUFFER_LEN] = {0};
    BEGET_ERROR_CHECK(GetBlockDevicePath("/bootctrl", bootctrlDev, MAX_BUFFER_LEN) == 0,
        return -1, "Failed to get bootctrl device");
    char *realPath = GetRealPath(bootctrlDev);
    BEGET_ERROR_CHECK(realPath != NULL, return -1, "Failed to get bootctrl device real path");
    int fd = open(realPath, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    free(realPath);
    BEGET_ERROR_CHECK(fd >= 0, return -1, "Failed to open bootctrl device, errno %d", errno);
    BEGET_ERROR_CHECK(lseek(fd, offset, SEEK_SET) >= 0, close(fd); return -1,
        "Failed to lseek bootctrl device fd, errno %d", errno);
    BEGET_INFO_CHECK(write(fd, &slotInfo, sizeof(slotInfo)) == size, close(fd); return -1,
        "Failed to write slot info to bootctrl, errno %d", errno);
    close(fd);
    BEGET_LOGI("set slot info: %d", slotInfo);
    return 0;
}

int SetCurrentCheckpoint(int checkpoint)
{
    return SetCheckpointToBootctrl(PARTITION_CHECKPOINT_OFFSET, PARTITION_CHECKPOINT_SIZE, checkpoint);
}

int SetCurrentSlot(int currentSlot)
{
    return SetSlotInfoToBootctrl(PARTITION_ACTIVE_SLOT_OFFSET, PARTITION_ACTIVE_SLOT_SIZE, currentSlot);
}

enum ReturnSetOtaStatus {
    SET_OTA_STATUS_FAILED = -1, // Failed to call SetOTAStatus to set information.
    BOOT_SUCCESS_IN_OTA = 0,    // In the ota stage, startup is complete.
    BOOT_SUCCESS_IN_NORMAL = 1, // In the non-ota stage, startup is complete.
    BOOT_FAILD_IN_OTA = 2,      // In the ota stage, startup is failed.
    BOOT_FAILD_IN_NORMAL = 3,   // In the non-ota stage, startup is failed.
};

int SetOTAStatus(int status)
{
    int32_t retGetcheckpoint = GetCurrentCheckpoint();
    if (retGetcheckpoint != 0 || retGetcheckpoint == -1) {
        BEGET_LOGE("Get checkpoint error");
        return SET_OTA_STATUS_FAILED;
    }
    // In the ota stage, startup is failed
    if (status == 0 && checkpoint == 1) {
        BEGET_LOGI("OTA upgrade failed, need to reboot and rollback");
        // switch partition
        SetActiveSlot();
        // Write checkpoint=0 to prevent remounting the data partition the next time it is accessed
        // Upgrade fails, reboot to do userdata rollback
        int retSetcheckpoint = SetCurrentCheckpoint(0);
        if (retSetcheckpoint != 0) {
            return SET_OTA_STATUS_FAILED;
        }
        return BOOT_FAILD_IN_OTA;
    // In the ota stage, startup is complete.
    } else if (status == 1 && checkpoint == 1) {
        BEGET_LOGI("OTA upgrade success");
        // Write checkpoint=0 to prevent remounting the data partition the next time it is accessed
        int retSetcheckpoint = SetCurrentCheckpoint(0);
        if (retSetcheckpoint != 0) {
            return SET_OTA_STATUS_FAILED;
        }
        return BOOT_SUCCESS_IN_OTA;
    // In the non-ota stage, startup is complete
    } else if (status == 1 && checkpoint == 0) {
        BEGET_LOGI("Normal boot success");
        return BOOT_SUCCESS_IN_NORMAL;
    // In the non-ota stage, startup is failed
    } else if (status == 0 && checkpoint == 0) {
        BEGET_LOGI("Normal boot failed");
        return BOOT_FAILD_IN_NORMAL;
    }
    return SET_OTA_STATUS_FAILED;
}
#else
int SetOTAStatus(int status)
{
    (void)status;
    return -1;
}
#endif